package repository

import (
	"context"
	"database/sql"
	"errors"
	"time"

	// "gitee.com/hp-huiw/family-expense-api/internal/db"
	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	"gitee.com/hp-huiw/my-go-core/date"

	// "gitee.com/hp-huiw/family-expense-api/internal/db/table"
	"gitee.com/hp-huiw/family-expense-api/internal/db"
	jet "github.com/go-jet/jet/v2/mysql"
	"github.com/go-jet/jet/v2/qrm"
)

type UserActiveTokenDevice struct {
	ID          string    `db:"id"`
	DeviceID    string    `db:"device_id"`
	DeviceModel string    `db:"device_model"`
	Enabled     bool      `db:"enabled"`
	LastUpdated time.Time `db:"last_updated"`
}

type UserActiveToken struct {
	Username    string  `db:"username"`
	DeviceID    *string `db:"device_id"`
	AccessToken string  `db:"access_token"`
}

type UserActiveTokenRepository interface {
	Exists(ctx context.Context, username string, deviceID *string, refreshToken string) (bool, error)
	Query(ctx context.Context) ([]UserActiveToken, error)
	QueryDevice(ctx context.Context, username string) ([]UserActiveTokenDevice, error)
	QueryToken(ctx context.Context, username string, deviceID *string) (*dbmodel.UserActiveToken, error)
	Insert(ctx context.Context, model *dbmodel.UserActiveToken) error
	Update(ctx context.Context, model *dbmodel.UserActiveToken) error
	UpdateEnabled(ctx context.Context, id string, username string, enabled bool) error
	DeleteByAccessToken(ctx context.Context, accessToken string) error
	DeleteByIDAndUsername(ctx context.Context, id string, username string) error
	DeleteByUserAndAccountGroupWithTx(ctx context.Context, tx *sql.Tx, username string, accountGroupID string) error
	DeleteByAccountGroupWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string) error
}

func NewUserActiveTokenRepository(db *sql.DB) UserActiveTokenRepository {
	return &userActiveTokenRepository{db: db}
}

func (r *userActiveTokenRepository) Exists(ctx context.Context, username string, deviceID *string, refreshToken string) (bool, error) {
	condition := tbActiveToken.Username.EQ(jet.String(username)).
		AND(tbActiveToken.RefreshToken.EQ(jet.String(refreshToken))).
		AND(tbActiveToken.Enabled.IS_TRUE())

	if deviceID == nil {
		condition = condition.AND(tbActiveToken.DeviceID.IS_NULL())
	} else {
		condition = condition.AND(tbActiveToken.DeviceID.EQ(jet.String(*deviceID)))
	}

	return db.Exists(ctx, r.db, tbActiveToken, condition)
}

func (r *userActiveTokenRepository) Query(ctx context.Context) ([]UserActiveToken, error) {
	var query []struct {
		Username    string  `db:"username"`
		DeviceID    *string `db:"device_id"`
		AccessToken string  `db:"access_token"`
	}

	stmt := jet.
		SELECT(
			tbActiveToken.Username.AS("username"),
			tbActiveToken.DeviceID.AS("device_id"),
			tbActiveToken.AccessToken.AS("access_token"),
		).
		FROM(
			tbActiveToken,
		).
		WHERE(
			tbActiveToken.DateExpired.GT_EQ(jet.DateTimeT(date.GetCurrentDate())).
				AND(tbActiveToken.Enabled.IS_TRUE()),
		)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		return nil, err
	}

	result := make([]UserActiveToken, 0, len(query))

	for _, item := range query {
		result = append(result, item)
	}

	return result, nil
}

// QueryDevice queries all devices which logged in with provided username.
func (r *userActiveTokenRepository) QueryDevice(ctx context.Context, username string) ([]UserActiveTokenDevice, error) {
	var query []struct {
		ID          string    `db:"id"`
		DeviceID    string    `db:"device_id"`
		DeviceModel string    `db:"device_model"`
		Enabled     bool      `db:"enabled"`
		LastUpdated time.Time `db:"last_updated"`
	}

	stmt := jet.
		SELECT(
			tbActiveToken.ID.AS("id"),
			tbActiveToken.DeviceID.AS("device_id"),
			tbActiveToken.DeviceModel.AS("device_model"),
			tbActiveToken.Enabled.AS("enabled"),
			tbActiveToken.LastUpdated.AS("last_updated"),
		).
		FROM(tbActiveToken).
		WHERE(
			tbActiveToken.Username.EQ(jet.String(username)),
		).
		ORDER_BY(
			tbActiveToken.LastUpdated.DESC(),
		)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		return nil, err
	}

	result := make([]UserActiveTokenDevice, 0, len(query))

	for _, item := range query {
		result = append(result, item)
	}

	return result, nil
}

func (r *userActiveTokenRepository) QueryToken(ctx context.Context, username string, deviceID *string) (*dbmodel.UserActiveToken, error) {
	var query dbmodel.UserActiveToken

	condition := tbActiveToken.Username.EQ(jet.String(username))

	if deviceID == nil {
		condition = condition.AND(
			jet.OR(
				tbActiveToken.DeviceID.IS_NULL(),
				tbActiveToken.DeviceID.EQ(jet.String("")),
			),
		)
	} else {
		condition = condition.AND(tbActiveToken.DeviceID.EQ(jet.String(*deviceID)))
	}

	stmt := jet.
		SELECT(
			tbActiveToken.AllColumns,
		).
		FROM(
			tbActiveToken,
		).
		WHERE(
			condition,
		)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		if errors.Is(err, qrm.ErrNoRows) {
			return nil, nil
		}

		return nil, err
	}

	return &query, nil
}

func (r *userActiveTokenRepository) Insert(ctx context.Context, model *dbmodel.UserActiveToken) error {
	stmt := tbActiveToken.
		INSERT().
		MODEL(model)

	_, err := stmt.ExecContext(ctx, r.db)

	return err
}

func (r *userActiveTokenRepository) Update(ctx context.Context, model *dbmodel.UserActiveToken) error {
	_, err := tbActiveToken.
		UPDATE(tbActiveToken.MutableColumns).
		MODEL(model).
		WHERE(
			tbActiveToken.ID.EQ(jet.String(model.ID)),
		).
		Exec(r.db)

	return err
}

func (r *userActiveTokenRepository) UpdateEnabled(ctx context.Context, id string, username string, enabled bool) error {
	stmt := tbActiveToken.UPDATE().
		SET(
			tbActiveToken.Enabled.SET(jet.Bool(enabled)),
		).
		WHERE(
			tbActiveToken.ID.EQ(jet.String(id)).
				AND(tbActiveToken.Username.EQ(jet.String(username))),
		)

	_, err := stmt.ExecContext(ctx, r.db)

	return err
}

func (r *userActiveTokenRepository) DeleteByAccessToken(ctx context.Context, accessToken string) error {
	stmt := tbActiveToken.
		DELETE().
		WHERE(
			tbActiveToken.AccessToken.EQ(jet.String(accessToken)),
		)

	_, err := stmt.ExecContext(ctx, r.db)

	return err
}

func (r *userActiveTokenRepository) DeleteByIDAndUsername(ctx context.Context, id string, username string) error {
	stmt := tbActiveToken.
		DELETE().
		WHERE(
			tbActiveToken.ID.EQ(jet.String(id)).
				AND(tbActiveToken.Username.EQ(jet.String(username))),
		)

	_, err := stmt.ExecContext(ctx, r.db)

	return err
}

func (*userActiveTokenRepository) DeleteByUserAndAccountGroupWithTx(ctx context.Context, tx *sql.Tx, username string, accountGroupID string) error {
	stmt := tbActiveToken.
		DELETE().
		WHERE(
			tbActiveToken.Username.EQ(jet.String(username)).
				AND(tbActiveToken.AccountGroupID.EQ(jet.String(accountGroupID))),
		)

	_, err := stmt.ExecContext(ctx, tx)

	return err
}

func (*userActiveTokenRepository) DeleteByAccountGroupWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string) error {
	stmt := tbActiveToken.
		DELETE().
		WHERE(
			tbActiveToken.AccountGroupID.EQ(jet.String(accountGroupID)),
		)

	_, err := stmt.ExecContext(ctx, tx)

	return err
}

type userActiveTokenRepository struct {
	db *sql.DB
}
